/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "main_writer.h"

#include <string>
#include <sys/types.h>
#include <cinttypes>
#include <ctime>

#include "hmfs_utils.h"
#include "mkfs_format.h"
#include "securec.h"
#include "device_manager.h"
#include "hmfs_define.h"
#include "hmfs_encoding.h"
#include "hmfs_io.h"
#include "hmfs_common.h"
#include "hmfs_quota.h"

namespace OHOS {
namespace Hmfs {

MainAreaWriter::MainAreaWriter(HmfsMkfs *mkfs) : mkfs_(mkfs)
{
}

int32_t MainAreaWriter::Prepare()
{
    if (PrepareRootInode()) {
        return -1;
    }

    if (PrepareDefaultDentryRoot()) {
        return -1;
    }

    if (WriteRootInode()) {
        return -1;

    }
    if (WriteQfInodes()) {
        return -1;
    }

    if (WriteLpfInode()) {
        return -1;
    }

    if (DiscardObsoleteDnode()) {
        return -1;
    }

    if (WriteDentryBlock()) {
        return -1;
    }

    return 0;
}

int32_t MainAreaWriter::PrepareRootInode()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;
    HmfsData& hmfsData = mkfs_->hmfsData_;

    rootInode_ = std::make_unique<NodeOnDisk>();
    if (rootInode_ == nullptr) {
        return -1;
    }
    struct NodeData* rootInode = &rootInode_->node;
    memset_s(rootInode, sizeof(NodeOnDisk), 0, sizeof(NodeOnDisk));


    SetLeValue(rootInode->footer.nid, hmfsData.rootInode);
    rootInode->footer.ino = rootInode->footer.nid;
    SetLeValue(rootInode->footer.cpVer, 1);
    SetLeValue(rootInode->footer.nextBlkaddr, hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT + 1);

    SetLeValue(rootInode->i.iMode, 0x41ed);
    SetLeValue(rootInode->i.iLinks, (hmfsData.lpfIno) ? 3 : 2);
    SetLeValue(rootInode->i.iUid, cfgPara.rootUid);
    SetLeValue(rootInode->i.iGid, cfgPara.rootGid);
    SetLeValue(rootInode->i.iSize, HMFS_BLOCK_SIZE); /* dentry */
    SetLeValue(rootInode->i.iBlocks, 2);
    SetLeValue(rootInode->i.iAtime, GetTimeStamp());
    rootInode->i.iAtimeNsec = 0;
    SetLeValue(rootInode->i.iCtime, GetTimeStamp());
    rootInode->i.iCtimeNsec = 0;
    SetLeValue(rootInode->i.iMtime, GetTimeStamp());
    rootInode->i.iMtimeNsec = 0;
    rootInode->i.iGeneration = 0;
    rootInode->i.iXattrNid = 0;
    rootInode->i.iFlags = 0;
    SetLeValue(rootInode->i.iCurrentDepth, 1);
    SetLeValue(rootInode->i.iDirLevel, DEFAULT_DIR_LEVEL);

    if (cfgPara.features & HMFS_FEATURE_EXTRA_ATTR) {
        rootInode->i.iInline = HMFS_EXTRA_ATTR;
        SetLeValue(rootInode->i.iExtraIsize, HmfsCommon::GetInstance().CalcExtraIsize());
    }

    if (cfgPara.features & HMFS_FEATURE_PRJQUOTA) {
        SetLeValue(rootInode->i.iProjid, DEFAULT_PROJECT_ID);
    }

    if (cfgPara.features & HMFS_FEATURE_INODE_CRTIME) {
        SetLeValue(rootInode->i.iCrtime, GetTimeStamp());
        rootInode->i.iCrtimeNsec = 0;
    }

    if (cfgPara.features & HMFS_FEATURE_COMPRESSION) {
        rootInode->i.iCompressAlgrithm = 0;
        rootInode->i.iLogClusterSize = 0;
        rootInode->i.iPadding = 0;
    }

    uint32_t dataBlockCount = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_DATA_HOT] * BLOCKS_PER_SEGMENT;
    SetLeValue(rootInode->i.i_addr[HmfsCommon::GetInstance().GetExtraIsize(&rootInode->i)], dataBlockCount);

    rootInode->i.iExt.fofs = 0;
    rootInode->i.iExt.blkAddr = 0;
    rootInode->i.iExt.len = 0;

    return 0;
}

uint64_t MainAreaWriter::GetTimeStamp()
{
    if (mkfs_->cfgPara_.timeStamp == std::numeric_limits<uint32_t>::max()) {
        return time(NULL);
    } else {
        return mkfs_->cfgPara_.timeStamp;
    }
}

int32_t MainAreaWriter::Write()
{
    return 0;
}

int32_t MainAreaWriter::WriteRootInode()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT;

    HMFS_DEBUG("Writing root inode (hot node), 0x%x 0x%x at blockId 0x%" PRIx64 "",
        hmfsData.mainStartBlkId, hmfsData.curSeg[CURSEG_NODE_HOT], blockId);

    if (HmfsCommon::GetInstance().WriteInode(&rootInode_->node, blockId, hmfsData.chksumSeed) < 0) {
        HMFS_ERROR("failed to write the root inode to disk.");
        return -1;
    }
    return 0;
}

int32_t MainAreaWriter::WriteQfInodes()
{
    CmdConfig& cfgPara = mkfs_->cfgPara_;

    uint32_t i = 0;
    for (int32_t qtype = 0; qtype < MAXQUOTAS; qtype++) {
        if (!((1 << qtype) & cfgPara.quotaBits)) {
            continue;
        }
        
        if (WriteQfInode(qtype, i++)) {
            HMFS_ERROR("Failed to write quota inode");
        }
    }

    return 0;
}

int32_t MainAreaWriter::WriteQfInode(int32_t qtype, uint32_t offset)
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    struct SuperBlockData* superBlock = mkfs_->GetSuperBlockData();
    if (superBlock == nullptr) {
        return -1;
    }

    auto nodeBuf = std::make_unique<NodeOnDisk>();
    if (nodeBuf == nullptr) {
        HMFS_ERROR("not enough memory for quota inode");
        return -1;
    }
    struct NodeData* qfInode = &nodeBuf->node;
    memset_s(qfInode, sizeof(NodeOnDisk), 0, sizeof(NodeOnDisk));

    HmfsCommon::GetInstance().InitQfInode(qfInode, GetLeValue(superBlock->qfInodeId[qtype]), GetTimeStamp());

    SetLeValue(qfInode->footer.nextBlkaddr,
        hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT + 1 + qtype + 1);
    SetLeValue(qfInode->i.iBlocks, 1 + QUOTA_DATA_BLOCK_COUNT);

    uint64_t dataBlkId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_DATA_HOT] * BLOCKS_PER_SEGMENT + 1 +
        offset * QUOTA_DATA_BLOCK_COUNT;
    uint32_t id = qfInode->i.iUid;
    if (qtype == GRPQUOTA) {
        id = qfInode->i.iGid;
    } else if (qtype == PRJQUOTA) {
        id = qfInode->i.iProjid;
    }

    if (WriteDefaultQuotaData(qtype, dataBlkId, id)) {
        return -1;
    }

    for (uint32_t i = 0; i < QUOTA_DATA_BLOCK_COUNT; i++) {
        SetLeValue(qfInode->i.i_addr[HmfsCommon::GetInstance().GetExtraIsize(&qfInode->i) + i], dataBlkId + i);
    }

    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT + offset + 1;

    HMFS_DEBUG("Writing quota inode (hot node), 0x%x 0x%x 0x%x at blockId 0x%" PRIx64 ".",
        hmfsData.mainStartBlkId, hmfsData.curSeg[CURSEG_HOT_NODE], BLOCKS_PER_SEGMENT, blockId);

    if (HmfsCommon::GetInstance().WriteInode(qfInode, blockId, hmfsData.chksumSeed) < 0) {
        HMFS_ERROR("Failed to write the qfInode to disk.");
        return -1;
    }

    hmfsData.quotaInodeCount++;

    return 0;
}

int32_t MainAreaWriter::WriteDefaultQuotaData(int32_t qtype, uint64_t dataBlkId, uint32_t id)
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    uint32_t bufLen = HMFS_BLOCK_SIZE * QUOTA_DATA_BLOCK_COUNT;
    auto buf = std::make_unique<uint8_t[]>(bufLen);
    if (buf == nullptr) {
        HMFS_ERROR("not enough memory for quota data");
        return -1;
    }
    memset_s(buf.get(), bufLen, 0, bufLen);

    /* basic quota header */
    DiskQuotaHeader* dqHeader = reinterpret_cast<DiskQuotaHeader*>(buf.get());
    SetLeValue(dqHeader->dqhMagic, INIT_QUOTA_MAGICS[qtype]);
    SetLeValue(dqHeader->dqhVersion, 1);   /* only support QF_VFSV1 */

    /* Initial quota file content */
    DiskQuotaInfo* dqInfo = reinterpret_cast<DiskQuotaInfo*>(dqHeader + 1);
    SetLeValue(dqInfo->dqiBgrace, MAX_DQ_TIME);
    SetLeValue(dqInfo->dqiIgrace, MAX_IQ_TIME);
    dqInfo->dqiFlags = 0;
    SetLeValue(dqInfo->dqiBlocks, QT_TREEOFF + 5);
    dqInfo->dqiFreeBlk = 0;
    SetLeValue(dqInfo->dqiFreeEntry, 5);

    buf[1024] = 2;
    buf[2048] = 3;
    buf[3072] = 4;
    buf[4096] = 5;
    buf[5120 + 8] = 1;

    DiskQuotaBlock* dqBlock = reinterpret_cast<DiskQuotaBlock*>(buf.get() + 5136);
    dqBlock->dqbId = id;
    dqBlock->dqbPad = 0;
    dqBlock->dqbIhardlimit = 0;
    dqBlock->dqbIsoftlimit = 0;
    SetLeValue(dqBlock->dqbCurinodes, (hmfsData.lpfIno) ? 2 : 1);
    dqBlock->dqbBhardlimit = 0;
    dqBlock->dqbBsoftlimit = 0;
    SetLeValue(dqBlock->dqbCurspace, (hmfsData.lpfIno) ? 8192 : 4096);
    dqBlock->dqbBtime = 0;
    dqBlock->dqbItime = 0;

    if (HmfsIo::GetInstance().DevWriteBlock(buf.get(), dataBlkId) ||
        HmfsIo::GetInstance().DevWriteBlock(buf.get() + HMFS_BLOCK_SIZE, dataBlkId + 1)) {
        HMFS_ERROR("failed to write quota data block to disk.");
        return -1;
    }
    HMFS_DEBUG("Writing quota data, at block 0x%" PRIx64 ", 0x%" PRIx64 ".", dataBlkId, dataBlkId + 1);

    hmfsData.quotaDataBlks += QUOTA_DATA_BLOCK_COUNT;

    return 0;
}

int32_t MainAreaWriter::WriteLpfInode()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    CmdConfig& cfgPara = mkfs_->cfgPara_;

    if (!(cfgPara.features & HMFS_FEATURE_LOST_FOUND)) {
        return 0;
    }

    struct SuperBlockData* superBlock = mkfs_->GetSuperBlockData();
    if (superBlock == nullptr) {
        return -1;
    }

    auto nodeBuf = std::make_unique<NodeOnDisk>();
    if (nodeBuf == nullptr) {
        HMFS_ERROR("not enough memory for lpf inode");
        return -1;
    }
    struct NodeData* lpfInode = &nodeBuf->node;
    memset_s(lpfInode, sizeof(NodeOnDisk), 0, sizeof(NodeOnDisk));

    lpfInode->footer.nid = NATIVE_TO_LE32(hmfsData.lpfIno);
    lpfInode->footer.ino = lpfInode->footer.nid;
    lpfInode->footer.cpVer = NATIVE_TO_LE64(1);
    lpfInode->footer.nextBlkaddr = NATIVE_TO_LE32(hmfsData.mainStartBlkId +
        hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT + 1 + hmfsData.quotaInodeCount + 1);

    lpfInode->i.iMode = NATIVE_TO_LE16(0x41c0); /* 0700 */
    lpfInode->i.iLinks = NATIVE_TO_LE32(2);
    lpfInode->i.iUid = NATIVE_TO_LE32(cfgPara.rootUid);
    lpfInode->i.iGid = NATIVE_TO_LE32(cfgPara.rootGid);

    lpfInode->i.iSize = NATIVE_TO_LE64(HMFS_BLOCK_SIZE);
    lpfInode->i.iBlocks = NATIVE_TO_LE64(2);

    lpfInode->i.iAtime = NATIVE_TO_LE32(GetTimeStamp());
    lpfInode->i.iAtimeNsec = 0;
    lpfInode->i.iCtime = NATIVE_TO_LE32(GetTimeStamp());
    lpfInode->i.iCtimeNsec = 0;
    lpfInode->i.iMtime = NATIVE_TO_LE32(GetTimeStamp());
    lpfInode->i.iMtimeNsec = 0;
    lpfInode->i.iGeneration = 0;
    lpfInode->i.iXattrNid = 0;
    lpfInode->i.iFlags = 0;
    lpfInode->i.iPino = NATIVE_TO_LE32(hmfsData.rootInode);
    lpfInode->i.iNamelen = NATIVE_TO_LE32(strlen(LPF_STRING));
    memcpy_s(lpfInode->i.iName, HMFS_NAME_LEN, LPF_STRING, strlen(LPF_STRING));
    lpfInode->i.iCurrentDepth = NATIVE_TO_LE32(1);
    lpfInode->i.iDirLevel = DEFAULT_DIR_LEVEL;

    if (cfgPara.features & HMFS_FEATURE_EXTRA_ATTR) {
        lpfInode->i.iInline = HMFS_EXTRA_ATTR;
        lpfInode->i.iExtraIsize = NATIVE_TO_LE16(HmfsCommon::GetInstance().CalcExtraIsize());
    }

    if (cfgPara.features & HMFS_FEATURE_PRJQUOTA) {
        SetLeValue(lpfInode->i.iProjid, DEFAULT_PROJECT_ID);
    }

    if (cfgPara.features & HMFS_FEATURE_INODE_CRTIME) {
        SetLeValue(lpfInode->i.iCrtime, GetTimeStamp());
        lpfInode->i.iCrtimeNsec = 0;
    }

    if (cfgPara.features & HMFS_FEATURE_COMPRESSION) {
        lpfInode->i.iCompressAlgrithm = 0;
        lpfInode->i.iLogClusterSize = 0;
        lpfInode->i.iPadding = 0;
    }

    uint32_t dataBlockId = WriteDefaultLpfDentry();
    if (dataBlockId == 0) {
        HMFS_ERROR("Failed to add default dentries for lost+found");
        return -1;
    }
    lpfInode->i.i_addr[HmfsCommon::GetInstance().GetExtraIsize(&lpfInode->i)] = NATIVE_TO_LE32(dataBlockId);
    
    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT +
        hmfsData.quotaInodeCount + 1;

    HMFS_DEBUG("Writing lost+found inode (hot node), 0x%x 0x%x 0x%x at offset 0x%" PRIx64 ".",
        hmfsData.mainStartBlkId, hmfsData.curSeg[CURSEG_NODE_HOT], BLOCKS_PER_SEGMENT, blockId);
    if (HmfsCommon::GetInstance().WriteInode(lpfInode, blockId, hmfsData.chksumSeed) < 0) {
        HMFS_ERROR("Failed to write the lost+found inode to disk.");
        return -1;
    }

    hmfsData.lpfInum++;
    return 0;
}

uint32_t MainAreaWriter::WriteDefaultLpfDentry(void)
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    auto buf = std::make_unique<DentryBlock>();
    if (buf == nullptr) {
        HMFS_ERROR("Not enough memory for lpf dentry block.");
        return -1;
    }
    struct DentryBlock* dentryBlock = buf.get();
    memset_s(dentryBlock, sizeof(DentryBlock), 0, sizeof(DentryBlock));

    std::vector<const char*> defaultDirList = {".", ".."};
    for (size_t i = 0; i < defaultDirList.size(); i++) {
        dentryBlock->dentry[i].hash_code = 0;
        SetLeValue(dentryBlock->dentry[i].ino, (i == 0) ? hmfsData.lpfIno : hmfsData.rootInode);
        SetLeValue(dentryBlock->dentry[i].nameLen, strlen(defaultDirList[i]));
        dentryBlock->dentry[i].fileType = HMFS_FT_DIR;
        memcpy_s(dentryBlock->filename[i], HMFS_SLOT_LEN, defaultDirList[i], strlen(defaultDirList[i]));

        HmfsCommon::GetInstance().TestAndSetBitLe(i, dentryBlock->dentryBitmap);
    }

    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_DATA_HOT] * BLOCKS_PER_SEGMENT + 1 +
        hmfsData.quotaDataBlks;
    HMFS_DEBUG("Writing default dentry lost+found, at offset 0x%" PRIx64 ".", blockId);
    if (HmfsIo::GetInstance().DevWriteBlock(dentryBlock, blockId)) {
        HMFS_ERROR("Failed to write lost+found dentry block to disk.");
        return 0;
    }

    hmfsData.lpfDnum++;
    return blockId;
}

int32_t MainAreaWriter::DiscardObsoleteDnode()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;
    CmdConfig& cfgPara = mkfs_->cfgPara_;

    if (cfgPara.zonedMode || (cfgPara.features & HMFS_FEATURE_RO)) {
        return 0;
    }

    auto node = std::make_unique<NodeData>();
    if (node == nullptr) {
        return -1;
    }

    uint64_t start_inode_pos = hmfsData.mainStartBlkId;
    uint64_t last_inode_pos = start_inode_pos + hmfsData.curSeg[CURSEG_NODE_HOT] * BLOCKS_PER_SEGMENT +
        hmfsData.quotaInodeCount + hmfsData.lpfInum;
    uint64_t endBlockId = hmfsData.mainStartBlkId + hmfsData.segmentCountInMain * BLOCKS_PER_SEGMENT;
    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_NODE_WARM] * BLOCKS_PER_SEGMENT;
    while ((blockId >= hmfsData.mainStartBlkId) && (blockId < endBlockId)) {
        if (HmfsIo::GetInstance().DevReadBlock(node.get(), blockId)) {
            HMFS_ERROR("failed to read block 0x%" PRIx64 " in traversing direct node", blockId);
            return -1;
        }
        uint64_t nextBlockId = GetLeValue(node->footer.nextBlkaddr);

        HMFS_DEBUG("erasing direct node 0x%" PRIx64 "", blockId);
        memset_s(node.get(), sizeof(NodeData), 0, sizeof(NodeData));
        if (HmfsIo::GetInstance().DevWriteBlock(node.get(), blockId)) {
            HMFS_ERROR("failed to erase block 0x%" PRIx64 "", blockId);
            return -1;
        }

        if ((nextBlockId >= start_inode_pos) || (nextBlockId <= last_inode_pos)) {
            break;
        }
        blockId = nextBlockId;
    }

    return 0;
}

int32_t MainAreaWriter::PrepareDefaultDentryRoot()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    dentryBlk_ = std::make_unique<DentryBlock>();
    if (dentryBlk_ == nullptr) {
        return -1;
    }
    struct DentryBlock* dentryBlk = dentryBlk_.get();
    memset_s(dentryBlk, sizeof(DentryBlock), 0, sizeof(DentryBlock));

    std::vector<const char*> defaultDirList = {".", ".."};
    uint32_t index = 0;
    for (size_t i = 0; i < defaultDirList.size(); i++) {
        dentryBlk->dentry[index].hash_code = 0;
        SetLeValue(dentryBlk->dentry[index].ino, hmfsData.rootInode);
        SetLeValue(dentryBlk->dentry[index].nameLen, strlen(defaultDirList[i]));
        dentryBlk->dentry[index].fileType = HMFS_FT_DIR;
        memcpy_s(dentryBlk->filename[index], HMFS_SLOT_LEN, defaultDirList[i], strlen(defaultDirList[i]));

        HmfsCommon::GetInstance().TestAndSetBitLe(index, dentryBlk->dentryBitmap);
        index++;
    }

    if (hmfsData.lpfIno) {
        int len = strlen(LPF_STRING);
        uint32_t hash = HmfsCommon::GetInstance().DentryHash(0, 0, (unsigned char *)LPF_STRING, len);

        dentryBlk->dentry[index].hash_code = NATIVE_TO_LE32(hash);
        dentryBlk->dentry[index].ino = NATIVE_TO_LE32(hmfsData.lpfIno);
        dentryBlk->dentry[index].nameLen = NATIVE_TO_LE16(len);
        dentryBlk->dentry[index].fileType = HMFS_FT_DIR;
        memcpy(dentryBlk->filename[index], LPF_STRING, HMFS_SLOT_LEN);
        HmfsCommon::GetInstance().TestAndSetBitLe(index, dentryBlk->dentryBitmap);
        index++;

        memcpy(dentryBlk->filename[index], &LPF_STRING[HMFS_SLOT_LEN], len - HMFS_SLOT_LEN);
        HmfsCommon::GetInstance().TestAndSetBitLe(index, dentryBlk->dentryBitmap);
    }

    return 0;
}

int32_t MainAreaWriter::WriteDentryBlock()
{
    HmfsData& hmfsData = mkfs_->hmfsData_;

    uint64_t blockId = hmfsData.mainStartBlkId + hmfsData.curSeg[CURSEG_DATA_HOT] * BLOCKS_PER_SEGMENT;

    HMFS_DEBUG("Writing default dentry root, at offset 0x%" PRIx64 "", blockId);
    if (HmfsIo::GetInstance().DevWriteBlock(dentryBlk_.get(), blockId) < 0) {
        HMFS_ERROR("failed to write the dentry block to disk.");
        return -1;
    }

    return 0;
}



} // namespace Hmfs
} // namespace OHOS
